package org.egrit;

import java.awt.Point;

import com.google.common.base.Predicate;
import com.vividsolutions.jts.geom.Polygon;


/**
 * Provides static methods for common grid calculations.
 *
 * @author michael
 */
public class GridOp {
    
    /**
     * Calculates sum of values in {@code dataGrid} for cell positions provided by
     * the given iterator.
     * 
     * @param dataGrid grid with values to sum
     * @param iter iterator providing cell positions
     * 
     * @return sum of values
     */
    public static double sum(IGrid dataGrid, IGridIterator iter) {
        double sum = 0;
        
        do {
            Point pos = iter.getPos();
            sum += dataGrid.getGridValue(pos).doubleValue();
        } while (iter.next());
        
        return sum;
    }

    /**
     * Calculates sum of values in {@code dataGrid} for cells that correspond to 
     * the value {@code zone} in {@code zoneGrid} and are within the specified bounds.
     * 
     * @param dataGrid grid with values to sum
     * @param zoneGrid controlling zone grid
     * @param zone target zone value
     * @param gridBounds area within which to examine cells
     * 
     * @return sum of values
     */
    public static double zonalSum(IGrid dataGrid, IGrid zoneGrid, int zone, IGridBounds gridBounds) {
        return zonalSum(dataGrid, zoneGrid, zone, gridBounds.getIterator());
    }

    /**
     * Calculates sum of values in {@code dataGrid} for cells that correspond to 
     * the value {@code zone} in {@code zoneGrid} and lie within the given polygon.
     * 
     * @param dataGrid grid with values to sum
     * @param zoneGrid controlling zone grid
     * @param zone target zone value
     * @param boundingPoly polygon defining the region in which to examine cells
     * 
     * @return sum of values
     */
    public static double zonalSum(IGrid dataGrid, IGrid zoneGrid, int zone, Polygon boundingPoly) {
        return zonalSum(dataGrid, zoneGrid, zone, new GridGeometryIterator(boundingPoly));
    }

    /**
     * Calculates sum of values in {@code dataGrid} for cell positions provided by
     * the given iterator for which the value in {@code zoneGrid} is {@code zone}.
     * 
     * @param dataGrid grid with values to sum
     * @param zoneGrid controlling zone grid
     * @param zone target zone value
     * @param iter iterator providing cell positions
     * 
     * @return sum of values
     */
    public static double zonalSum(IGrid dataGrid, IGrid zoneGrid, int zone, IGridIterator iter) {
        double sum = 0;
        
        do {
            Point pos = iter.getPos();
            if (zoneGrid.getGridValue(pos).intValue() == zone) {
                sum += dataGrid.getGridValue(pos).doubleValue();
            }
        } while (iter.next());
        
        return sum;
    }

    /**
     * Asserts that all grid values falling within the polygon satisfy the 
     * given value condition.
     * 
     * @param grid the grid
     * @param poly the polygon within which to search
     * @param cond the value condition
     * 
     * @return {@code true} if all values satisfy the condition
     */
    public static boolean assertCondition(IGrid grid, Polygon poly, Predicate<Number> cond) {
        GridGeometryIterator iter = new GridGeometryIterator(poly);
        return assertCondition(grid, iter, cond);
    }

    /**
     * Asserts that all grid values at positions provided by the iterator satisfy the 
     * given value condition.
     * 
     * @param grid the grid
     * @param iter iterator providing grid positions at which to test values
     * @param cond the value condition
     * 
     * @return {@code true} if all values satisfy the condition
     */
    public static boolean assertCondition(IGrid grid, IGridIterator iter, Predicate<Number> cond) {
        if (iter.isEmpty()) {
            throw new IllegalStateException("iterator has no grid positions");
        }
        
        do {
            if (!cond.apply(grid.getGridValue(iter.getPos()))) {
                return false;
            }
        } while (iter.next());
        
        return true;
    }
    
}
